using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using FluentIoC.Configuration.Exceptions;
using FluentIoC.Helpers;
using FluentIoC.Interfaces;

namespace FluentIoC.Configuration.Commands
{
    public class RegisterAssembly : RegisterProperties, IRegistration
    {
        private IConventions _assemblyConventions;
        private IEnumerable<RegisterService> _registrations = new List<RegisterService>();

        private IEnumerable<RegisterService> Registrations
        {
            get
            {
                if(null != _assemblyConventions)
                {
                    _registrations = _registrations.Where(r => r.IsValid(_assemblyConventions));
                }

                return _registrations;
            }
        }

        public Dictionary<Type, string> Services
        {
            get
            {
                var services = new Dictionary<Type, string>();
                foreach(var registration in Registrations)
                {
                    services[registration.ServiceType] = registration.ServiceKey;
                }
                return services;
            }
        }

        public RegisterAssembly(string assembly)
        {
            _registrations = GetServiceRegistration(assembly);
        }

        private IEnumerable<RegisterService> GetServiceRegistration(string assembly)
        {
            var registrations = Assembly.Load(assembly).GetTypes().Select(t => new RegisterService(t));

            if(registrations.Count() == 0)
            {
                throw new ServicesNotFoundException(assembly);
            }

            return registrations;
        }

        public RegisterAssembly ImplementedBy(string assembly)
        {
            var types = Assembly.Load(assembly).GetTypes();

            if (0 == types.Count())
            {
                throw new ComponentsNotFoundException(assembly);
            }

            _registrations = CompleteRegistrations(_registrations, types);

            return this;
        }

        private IEnumerable<RegisterService> CompleteRegistrations(
            IEnumerable<RegisterService> serviceRegistrations, IEnumerable<Type> implementorTypes)
        {
            foreach(var registration in serviceRegistrations)
            {
                var components = implementorTypes.Where(t => t.Implements(registration.ServiceType)).ToArray();

                if (components.Length > 0)
                {
                    registration.ComponentType = components[0];

                    yield return registration;    

                    if (components.Length > 1)
                    {
                        for (int i = 1; i < components.Length; i++)
                        {
                            yield return new RegisterService(
                                registration.ServiceType,
                                registration.ServiceKey + new Guid(),
                                components[i]
                            );
                        }   
                    }
                }
            }
        }

        public RegisterAssembly UsingConventions(IConventions conventions)
        {
            _assemblyConventions = conventions;

            return this;
        }

        public void Configure(IIoCConfiguration config, Dictionary<Type, string> services)
        {
            foreach(var registration in Registrations)
            {
                registration.Properties = Properties;
                registration.Lifestyle = Lifestyle;
                
                registration.Configure(config, services);
            }
        }

        public bool IsValid(IConventions conventions)
        {
            _registrations = Registrations.Where(s => s.IsValid(conventions));

            return true;
        }
    }
}